

using System;
using System.Numerics;

namespace NodeBasedSkillEditor.Model.Node.RT
{
    public class Node_Random_RT : RT_Node
    {
        public RT_SingleValuePort Distribution_RT;

        public RT_SingleValuePort VaryMode_RT;

        public RT_SingleValuePort AutoSeed_RT;

        public RT_SingleValuePort Seed_RT;

        public RT_SingleValuePort Index_RT;

        public RT_SingleValuePort Mean_RT;

        public RT_SingleValuePort Variance_RT;

        public RT_SingleValuePort Result_RT;

        public Node_Random_RT(Node_Random sourceData, RT_DAG parentDag) : base(sourceData, parentDag)
        {
            Distribution_RT = new RT_SingleValuePort(EPortIOType.Input, sourceData.Distribution, this);

            VaryMode_RT = new RT_SingleValuePort(EPortIOType.Input, sourceData.VaryMode, this);

            AutoSeed_RT = new RT_SingleValuePort(EPortIOType.Input, sourceData.AutoSeed, this);

            Seed_RT = new RT_SingleValuePort(EPortIOType.Input, sourceData.Seed, this);

            Index_RT = new RT_SingleValuePort(EPortIOType.Input, sourceData.Index, this);

            Mean_RT = new RT_SingleValuePort(EPortIOType.Input, sourceData.Mean, this);

            Variance_RT = new RT_SingleValuePort(EPortIOType.Input, sourceData.Variance, this);

            Result_RT = new RT_SingleValuePort(EPortIOType.Output, sourceData.Result, this);
            Result_RT.OnGetValue = GetResult;
        }

        public AValue GetResult(DAG_Executor executor)
        {
            ERandDistribution distriType = (ERandDistribution)Distribution_RT.GetValue(executor).ToEnum().Item2;
            bool autoSeed = AutoSeed_RT.GetValue(executor).ToBoolean().Item2;
            int seed = Seed_RT.GetValue(executor).ToInteger().Item2;
            int id = Index_RT.GetValue(executor).ToInteger().Item2;
            EVaryMode varyMode = (EVaryMode)VaryMode_RT.GetValue(executor).ToEnum().Item2;

            Random_Info info = ProcessInfo(executor, autoSeed, seed, distriType, varyMode);

            UpdateResultValue(info, id, executor);

            return Result_RT.SourceValue;
        }

        void UpdateResultValue(Random_Info info, int id, DAG_Executor executor)
        {
            var valueType = Result_RT.SourceValue.ValueType;
            switch (valueType)
            {
                case AValue.EValueType.Unknown:
                    break;
                case AValue.EValueType.Boolean:
                    Result_RT.SourceValue.SetBoolean(info.GetRandomBoolean(id));
                    break;
                case AValue.EValueType.Integer:
                    int iMean = Mean_RT.GetValue(executor).ToInteger().Item2;
                    int iVariance = Variance_RT.GetValue(executor).ToInteger().Item2;
                    Result_RT.SourceValue.SetInteger(info.GetRandomInt(id, iMean, iVariance));
                    break;
                case AValue.EValueType.Scalar:
                    float fMean = Mean_RT.GetValue(executor).ToScalar().Item2;
                    float fVariance = Variance_RT.GetValue(executor).ToScalar().Item2;
                    Result_RT.SourceValue.SetScalar(info.GetRandomFloat(id, fMean, fVariance));
                    break;
                case AValue.EValueType.Vector2:
                    Vector2 vec2Mean = Mean_RT.GetValue(executor).ToVector2().Item2;
                    Vector2 vec2Variance = Variance_RT.GetValue(executor).ToVector2().Item2;
                    Result_RT.SourceValue.SetVector2(info.GetRandomVector2(id, vec2Mean, vec2Variance));
                    break;
                case AValue.EValueType.Vector3:
                    Vector3 vec3Mean = Mean_RT.GetValue(executor).ToVector3().Item2;
                    Vector3 vec3Variance = Variance_RT.GetValue(executor).ToVector3().Item2;
                    Result_RT.SourceValue.SetVector3(info.GetRandomVector3(id, vec3Mean, vec3Variance));
                    break;
                default:
                    break;
            }
        }

        Random_Info ProcessInfo(DAG_Executor executor, bool autoSeed, int seed, ERandDistribution distribution, EVaryMode varyMode)
        {
            var valueType = Result_RT.SourceValue.ValueType;
            var info = executor.GetExeInfoOfNode(this) as Random_Info;
            if (info == null)
            {
                Random rand = CreateRandomGenerator(autoSeed, seed);
                info = new Random_Info(autoSeed, seed, rand, valueType, distribution);
                executor.SetExeInfoOfNode(this, info);
            }
            else
            {
                if (autoSeed != info.IsAutoSeed)
                {
                    Random rand = CreateRandomGenerator(autoSeed, seed);
                    info.RandomGenerator = rand;
                    info.UpdateRandomBase();
                }
                else if (!info.IsAutoSeed && seed != info.CurrentSeed)
                {
                    info.CurrentSeed = seed;
                    info.RandomGenerator = new Random(seed);
                    info.UpdateRandomBase();
                }
                else if (varyMode == EVaryMode.AlwaysVary
                    || (varyMode == EVaryMode.TimeVary && info.LastRunFrame != executor.CurrentFrame))
                {
                    info.UpdateRandomBase();
                }

            }
            info.LastRunFrame = executor.CurrentFrame;
            return info;
        }

        Random CreateRandomGenerator(bool autoSeed, int seed)
        {
            Random rand;
            if (autoSeed) { rand = new Random(XDFLib.XMath.RandomRange(0, 999999)); }
            else { rand = new Random(seed); }
            return rand;
        }
    }

    public class Random_Info : RT_NodeExeInfo
    {
        public AValue.EValueType ValueType;
        public ERandDistribution Distribution;
        public bool IsAutoSeed;
        public int CurrentSeed;
        public Random RandomGenerator;

        public int IdFactor { get; private set; }
        public int LastRunFrame = 0;
        public Vector3 CurrentRandomBase;

        public Random_Info(AValue.EValueType valueType, ERandDistribution distribution)
        {
            ValueType = valueType;
            Distribution = distribution;
            UpdateRandomBase();
        }

        public Random_Info(bool isAutoSeed, int currentSeed, Random rand, AValue.EValueType valueType, ERandDistribution distribution)
        {
            IsAutoSeed = isAutoSeed;
            CurrentSeed = currentSeed;
            RandomGenerator = rand;
            ValueType = valueType;
            Distribution = distribution;
            UpdateRandomBase();
        }

        public bool GetRandomBoolean(int id)
        {
            var (rand1, rand2) = GetRandom01ViaId_X(id);
            return rand1 > 0.5f;
        }

        public int GetRandomInt(int id, int mean, int variance)
        {
            var (rand1, rand2) = GetRandom01ViaId_X(id);
            int result = (int)From01ToZone(rand1, rand2, mean, variance);

            return result;
        }

        public float GetRandomFloat(int id, float mean, float variance)
        {
            var (rand1, rand2) = GetRandom01ViaId_X(id);

            float result = From01ToZone(rand1, rand2, mean, variance);

            return result;
        }

        public Vector2 GetRandomVector2(int id, Vector2 mean, Vector2 variance)
        {
            var (randX1, randX2) = GetRandom01ViaId_X(id);
            var (randY1, randY2) = GetRandom01ViaId_Y(id);

            float x = From01ToZone(randX1, randX2, mean.X, variance.X);
            float y = From01ToZone(randY1, randY2, mean.Y, variance.Y);
            Vector2 result = new Vector2(x, y);
            return result;
        }

        public Vector3 GetRandomVector3(int id, Vector3 mean, Vector3 variance)
        {
            var (randX1, randX2) = GetRandom01ViaId_X(id);
            var (randY1, randY2) = GetRandom01ViaId_Y(id);
            var (randZ1, randZ2) = GetRandom01ViaId_Z(id);

            float x = From01ToZone(randX1, randX2, mean.X, variance.X);
            float y = From01ToZone(randY1, randY2, mean.Y, variance.Y);
            float z = From01ToZone(randZ1, randZ2, mean.Z, variance.Z);
            Vector3 result = new Vector3(x, y, z);
            return result;
        }

        float From01ToZone(float random01a, float random01b, float mean, float variance)
        {
            float result;
            float min = mean - variance;
            float len = variance + variance;
            switch (Distribution)
            {
                case ERandDistribution.Gaussian:
                    result = XDFLib.XMath.GaussianDistribute(random01a, random01b, mean, variance);
                    break;
                case ERandDistribution.Triangle:
                    float max = mean + variance;
                    result = XDFLib.XMath.TriangleDistribute(random01a, min, max, mean);
                    break;

                case ERandDistribution.Uniform:
                default:
                    result = min + random01a * len;
                    break;
            }
            return result;
        }

        (float, float) GetRandom01ViaId_X(int id)
        {
            var id1 = id * IdFactor;
            var idRandom01a = XDFLib.XMath.LCGRandom01(id1);
            float random01a = CurrentRandomBase.X + idRandom01a;
            if (random01a > 1)
            {
                random01a -= MathF.Floor(random01a);
            }

            var id2 = id1 + 11;
            var idRandom01b = XDFLib.XMath.LCGRandom01(id2);
            float random01b = CurrentRandomBase.X + idRandom01b;
            if (random01b > 1)
            {
                random01b -= MathF.Floor(random01b);
            }

            return (random01a, idRandom01b);
        }

        (float, float) GetRandom01ViaId_Y(int id)
        {
            var id1 = id * IdFactor;
            var idRandom01a = XDFLib.XMath.LCGRandom01(id1);
            float random01a = CurrentRandomBase.Y + idRandom01a;
            if (random01a > 1)
            {
                random01a -= MathF.Floor(random01a);
            }

            var id2 = id1 + 11;
            var idRandom01b = XDFLib.XMath.LCGRandom01(id2);
            float random01b = CurrentRandomBase.Y + idRandom01b;
            if (random01b > 1)
            {
                random01b -= MathF.Floor(random01b);
            }

            return (random01a, idRandom01b);
        }

        (float, float) GetRandom01ViaId_Z(int id)
        {
            var id1 = id * IdFactor;
            var idRandom01a = XDFLib.XMath.LCGRandom01(id1);
            float random01a = CurrentRandomBase.Z + idRandom01a;
            if (random01a > 1)
            {
                random01a -= MathF.Floor(random01a);
            }

            var id2 = id1 + 11;
            var idRandom01b = XDFLib.XMath.LCGRandom01(id2);
            float random01b = CurrentRandomBase.Z + idRandom01b;
            if (random01b > 1)
            {
                random01b -= MathF.Floor(random01b);
            }

            return (random01a, idRandom01b);
        }


        public void UpdateRandomBase()
        {
            switch (ValueType)
            {
                case AValue.EValueType.Boolean:
                case AValue.EValueType.Integer:
                    CurrentRandomBase.X = (float)RandomGenerator.NextDouble();
                    break;
                case AValue.EValueType.Scalar:
                    CurrentRandomBase.X = (float)RandomGenerator.NextDouble();
                    break;
                case AValue.EValueType.Vector2:
                    CurrentRandomBase.X = (float)RandomGenerator.NextDouble();
                    CurrentRandomBase.Y = (float)RandomGenerator.NextDouble();
                    break;
                case AValue.EValueType.Vector3:
                    CurrentRandomBase.X = (float)RandomGenerator.NextDouble();
                    CurrentRandomBase.Y = (float)RandomGenerator.NextDouble();
                    CurrentRandomBase.Z = (float)RandomGenerator.NextDouble();
                    break;
                default:
                    break;
            }
            GenerateNewIdFactor();
        }

        public void GenerateNewIdFactor()
        {
            IdFactor = (int)MathF.Max(RandomGenerator.Next(), 1);
        }
    }
}